package com.hup.vd.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

/**
 * @author hugan
 * @date 2021/7/6
 */
public interface RestTemplateUtil {

    /**
     * <pre>
     * restTemplate调用代码封装方法:
     * .处理"headers和入参"的代码:不同HttpMethod,headers,contentType时,入参的代码会不一样
     * .处理"支持返回嵌套泛型对象"的代码
     * .具体入参要求,参考方法内注释
     * .特殊说明,为提高代码健壮性,建议使用: {@link #fixMessageConverter(RestTemplate)}
     *
     * @param headers nullable,headers用于指定contentType和cookies等, {@link #headerDemo()}<pre>
     *     contentType会影响到req的设置代码,
     * </pre>
     *
     * @param req nullable,入参数据对象
     * @throws HttpStatusCodeException http返回码异常:服务端正常返回"异常说明数据":如<br>
     * .调用方异常:404/入参/请求异常...<br>
     * .服务端异常:数据库/内部异常...<br>
     * 获取异常数据对象:JSON.parseObject(e.getResponseBodyAsString(), ExceptionResult.class)
     * @throws ResourceAccessException 服务器没启动,本机没联网
     */
    static <T> T getResponseData(String url, HttpMethod method, HttpHeaders headers, Object req,
            ParameterizedTypeReference<T> responseType, RestTemplate restTemplate)
            throws HttpStatusCodeException, ResourceAccessException {
        HttpEntity<?> requestEntity = null;
        if (HttpMethod.GET.equals(method)) {
            /*
            get请求:请求入参(若有时),都是拼接在url上的
                .url:不含问号和参数,如"host:port/xx/xx"
                .headers:正常的contentType是null,假如误设了setContentType(MediaType.APPLICATION_JSON);不影响后台接收入参,相当于没作用,因为请求入参都在url中
             */
            //将参数拼接到url上,并对非英文内容进行编码
            if (req != null) {
                JSONObject json = (JSONObject) JSON.toJSON(req);
                UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
                for (Entry<String, Object> entry : json.entrySet()) {
                    builder.queryParam(entry.getKey(), entry.getValue());
                }
                //build作用,对"非英文进行编码";不调用时,后台收到的中文是"%xx%xx"
                url = builder.build().toUriString();
            }
            requestEntity = new HttpEntity<>(headers);
        } else {
            /*
            post,put等除get外的请求
             */
            //将参数放入requestEntity
            if (req != null) {
                MediaType contentType = null;
                if (headers != null) contentType = headers.getContentType();
                if (contentType == null) contentType = MediaType.APPLICATION_JSON;
                if (MediaType.APPLICATION_JSON.equals(contentType)) {
                    //application/json
                    if (headers == null) headers = new HttpHeaders();
                    headers.setContentType(contentType);//处理前面的headers即使不为null,它的contentType也可能是null
                    requestEntity = new HttpEntity<>(req, headers);
                } else if (MediaType.MULTIPART_FORM_DATA.equals(contentType)) {
                    /*
                    multipart/form-data
                        .把对象放入LinkedMultiValueMap,注意LinkedMultiValueMap.add
                        .此处headers一定是notNull的
                     */
                    JSONObject json = (JSONObject) JSON.toJSON(req);
                    requestEntity = new HttpEntity<>(getReqObjectMap(json), headers);
                } else {
                    throw new IllegalArgumentException("不支持类型:" + contentType.toString());
                }
            } else {
                requestEntity = new HttpEntity<>(headers);
            }
        }
        return restTemplate.exchange(url, method, requestEntity, responseType).getBody();
    }

    /**
     * 用于multipart/form-data时,将req转成LinkedMultiValueMap;不支持嵌套对象
     */
    static LinkedMultiValueMap<String, Object> getReqObjectMap(JSONObject req) {
        LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
        for (Entry<String, Object> entry : req.entrySet()) {
            Object value = entry.getValue();
            if (value instanceof JSONObject) {
                throw new IllegalArgumentException("multipart/form-data不支持嵌套对象;application/json支持");
            } else {
                //支持list
                map.add(entry.getKey(), value);
            }
        }
        return map;
    }

    /**
     * <pre>
     * 修正"返回内容"的解码器为fastJson;
     * .原有的jackJson会在解析某些不规范的json数据时抛异常
     * .如果请求结果是"js-json数据文件",则jackJson也会抛异常
     */
    static RestTemplate fixMessageConverter(RestTemplate restTemplate) {
        List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
        Iterator<HttpMessageConverter<?>> it = converters.iterator();
        /*
        修改和移除解析器:目前非必要
        参考自:https://blog.csdn.net/u014327499/article/details/78391001
         */
        while (it.hasNext()) {
            HttpMessageConverter<?> next = it.next();
            if (next instanceof StringHttpMessageConverter) {
                //把原有的ISO-8859-1替换成UTF_8
                ((StringHttpMessageConverter) next).setDefaultCharset(StandardCharsets.UTF_8);
            } else if (next instanceof MappingJackson2CborHttpMessageConverter) {
                //移除Jackson
                it.remove();
            }
        }
        //将FastJson放在第一项(放后面是会被默认的jackJson拦截的)
        converters.add(0, new FastJsonHttpMessageConverter());
        //FastJsonConfig fastJsonConfig = new FastJsonConfig();
        //fastJsonConfig.setSerializerFeatures(
        //        SerializerFeature.WriteMapNullValue,
        //        SerializerFeature.WriteNullStringAsEmpty,
        //        SerializerFeature.WriteNullListAsEmpty,
        //        SerializerFeature.DisableCircularReferenceDetect);
        //
        //fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
        return restTemplate;
    }

    /**
     * HttpHeaders实例代码
     */
    static HttpHeaders headerDemo() {
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.ACCEPT_ENCODING, "application/json, text/plain, gzip");
        headers.set(HttpHeaders.USER_AGENT, "okhttp/3.11.0");
        headers.setContentType(MediaType.APPLICATION_JSON);
        List<String> cookies = new ArrayList<>();
        cookies.add("tid=123; path=/; domain=.xxx.com.cn");
        headers.put(HttpHeaders.COOKIE, cookies);
        return headers;
    }

    public static final String REDIRECT_URL_HEADER = "redirectLoc";

    /**
     * 说明:
     * .RestTemplate.get请求时,会自动处理重定向,但最终返回的 ResponseEntity中无法获取到最后请求的url
     * .本方法目的,把最终的url放入 ResponseEntity.headers.redirectURL
     * .获取方式 {@link #getRedirectUrl(ResponseEntity)}
     * .https://www.lmlphp.com/user/151781/article/item/8138674/
     * .https://blog.csdn.net/m0_37657841/article/details/107391699
     */
    static HttpComponentsClientHttpRequestFactory createRedirectFactory() {
        return new HttpComponentsClientHttpRequestFactory(HttpClientBuilder
                .create()
                .setRedirectStrategy(new DefaultRedirectStrategy() {
                    @Override
                    public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
                        boolean redirected = super.isRedirected(request, response, context);
                        /*
                        .请求是重定向时,将redirectURL放入context,供后续所有请求使用
                        .每次都将redirectURL放入response
                        .response中的redirectURL
                            .nullable, 没有发生过重定向
                            .notNull, 发生过重定向, 且值是最后一次重定向的url
                            .本次是重定向, 则url是: lastResponse.302的url,服务器给的
                            .本次不是重定向, 则url是: 当前request.url, 是上次请求前,本类放入的
                         */
                        if (redirected) {
                            //int statusCode = response.getStatusLine().getStatusCode();
                            String redirectURL = response.getFirstHeader("Location").getValue();
                            context.setAttribute(REDIRECT_URL_HEADER, redirectURL);
                        }
                        Object redirectURL = context.getAttribute(REDIRECT_URL_HEADER);
                        if (redirectURL != null) {
                            response.setHeader(REDIRECT_URL_HEADER, String.valueOf(redirectURL));
                        }
                        return redirected;
                    }
                }).build()
        );
    }

    static String getRedirectUrl(ResponseEntity<?> response) {
        List<String> list = response.getHeaders().get(REDIRECT_URL_HEADER);
        if (list != null && list.size() > 0) return list.get(0);
        return null;
    }

}
